﻿package we3d.loader 
{
	import flash.utils.Endian;
	import flash.utils.ByteArray;
	import we3d.mesh.Face;
	import we3d.mesh.VertexMap;
	import we3d.mesh.Vertex;
	
	import flash.utils.*;
	import flash.events.*;
	
	import we3d.core.Camera3d;
	import we3d.core.transform.All;
	import we3d.core.Object3d;
	import we3d.material.Surface;
	import we3d.material.FlatAttributes;
	import we3d.material.BitmapAttributes;
	import we3d.scene.SceneObject;
	import we3d.scene.SceneLight;
	import we3d.loader.Loader3d;
	import we3d.loader.ImageLoadEvent;
	import we3d.math.Matrix3d;
	import we3d.math.Vector3d;
	
	/**
	* Load Max 3DS Files. <br/>
	* <br/>
	* Support: <br/>
	* <br/>
	* - Objects with replaced translation (only position and rotation, scaling will be apllied to the mesh)<br/>
	* - Triangles <br/>
	* - Target Cameras <br/>
	* - Point Lights <br/>
	* - UV Mapping <br/>
	* <br/>
	* Material Properties: <br/>
	* - Diffuse Color <br/>
	* - Double sided <br/>
	* - Diffuse Bitmap Texture, note: the space for the texture file name in the 3ds file can have only 12 characters and may be upper case <br/>
	*/
	public class _3DSLoader extends SceneLoader 
	{
		private var piid:int=-1;
		private var bytes:ByteArray;
		private var uvs:Array;
		private var polys:Array;
		private var currType:String;
		private var currObj:Object3d; // current mesh light or camera
		
		private var trans:Matrix3d;
		private var lghts:int = 0;
		private var cams:int = 0;
		private var lghtCol:Boolean = false;
		private var textures:Array;
		private var sfname:String;
		
		public function _3DSLoader () {}
		
		public override function parseFile (b:ByteArray) :void {
			
			status = 0;
			super.init();
			
			trans = new Matrix3d();
			textures = [];
			sfname = "";
			currType = "";
			cams = lghts = 0;
			lghtCol = false;
			uvs = [];
			polys = [];
			bytes = b;
			bytes.endian = Endian.LITTLE_ENDIAN;
			
			while(bytes.bytesAvailable > 0) {
				parseSurfaceChunk();
			}
			
			bytes.position = 0;
			
			if(loadResources && images.length > 0) {
				addEventListener(EVT_IMAGES_LOADED, allImagesLoaded);
				addEventListener(EVT_IMAGE_LOADED, imageLoaded);
				var L:int = images.length;
				for(var i:int=0; i<L; i++) {
					loadBitmap(i);
				}
			}else{
				startParse();
			}
		}
		
		private function imageLoaded (e:ImageLoadEvent) :void {
			var sf:Surface;
			var imageid:int = e.imgid;
			var L:int = textures.length;
			var txtr:Object;
			
			for(var i:int=0; i<L; i++) {
				txtr = textures[i];
				if(txtr.id == imageid) {
					sf = getSurfaceAt(txtr.sfid);
					sf.attributes = new BitmapAttributes(bitmaps[e.bmpid].bmp);
					sf.rasterizer = defaultTextureRasterizer;
				}
			}
		}
		
		private function allImagesLoaded (e:Event) :void {
			removeEventListener(EVT_IMAGES_LOADED, allImagesLoaded);
			removeEventListener(EVT_IMAGE_LOADED, imageLoaded);
			startParse();
		}
		
		private function startParse () :void {
			if(blocking) {
				var L:int = bytes.length;
				while(bytes.position < L-8) {
					parseChunk();
				}
				finishParse();
			}else{
				if(piid != -1) clearInterval(piid);
				piid = setInterval(parseStep, loadParseInterval);
			}
		}
		
		private function parseStep () :void {
			status = int((bytes.position/bytes.length) * 100);
			for(var i:int=0; i<chunksPerFrame; i++) {
				if(bytes.position < bytes.length-8) {
					parseChunk();
				}else{
					finishParse();
					break;
				}
			}
		}
		
		private function parseSurfaceChunk () :void {
			
			var chunk:int = bytes.readUnsignedShort();
			var size:int = bytes.readUnsignedInt();
			
			switch (chunk) {
				case 0x3d3d: 
				case 0x4d4d:
				case 0xAFFF:
				case 0xA200:
					break;
					
				// MATERIAL_NAME
				case 0xA000:
					var name:String = readString();
					fileSurfaces.push( new Surface() );
					sfname = name;
					surfacesByName[name] = fileSurfaces[fileSurfaces.length-1];
					currSurface = surfacesByName[name];
					currSurface.rasterizer = defaultRasterizer;
					
					break;
					
				// DIFFUSE_COLOR
				case 0xA020:
					var mchunk:int = bytes.readUnsignedShort();
					var msize:int = bytes.readUnsignedInt();
					var cr:Number = bytes.readUnsignedInt();
					var rgb:Object = { r: cr>>16&255, g: cr>>8&255, b: cr&255};
					currSurface.attributes = new FlatAttributes(rgb.b << 16 | rgb.g << 8 | rgb.r);
					bytes.position += size-16;
					break;
				
				// DOUBLE_SIDED
				case 0xA081:
					currSurface.hideBackfaces = false;
					break;
					
				// TEXTURE_FILE
				case 0xA300:
					images.push(readString());
					textures.push({ id: images.length-1, sfid: fileSurfaces.length-1, name:sfname});
					break;
					
				default:
                    bytes.position += size-6;
			}
		}
		
		private function parseChunk () :void {
			
			var chunk:int = bytes.readUnsignedShort();
			var size:int = bytes.readUnsignedInt();
			var i:int;
			var L:int;
			var tsize:int;
			var name:String;
			 
			switch (chunk) {
				
				case 0x3d3d: 
				case 0x4d4d:
				case 0x4100: 
				//case 0x0013:
				//case 0x3DAA:
					break;
					
				// LIGHT_BLOCK
				case 0x4600:
					var posx:Number = bytes.readFloat();
					var posy:Number = bytes.readFloat();
					var posz:Number = bytes.readFloat();
					
					fileLights.push(new SceneLight(true));
					lightsByName["light_"+ lghts] = fileLights[fileLights.length-1];
					currLight = fileLights[fileLights.length-1];
					
					currLight.transform.x = bytes.readFloat() * scaleX;
					currLight.transform.z = bytes.readFloat() * scaleY;
					currLight.transform.y = bytes.readFloat() * scaleZ;
					
					currType = "light";
					currObj = currLight;
					
					lghtCol = true;
					lghts++
					
					break;
					
				// SPOT_LIGHT
				case 0x4610:
					if(currType=="light") currLight.directional = true;
					bytes.position += size-6;
					break;
					
				// RGB_COLOR
				case 0x0010:
					if(lghtCol) {
						currLight.r = bytes.readFloat()*255;
						currLight.g = bytes.readFloat()*255;
						currLight.b = bytes.readFloat()*255;
						lghtCol = false;
					}else{
						bytes.position += size-6;
					}
					break;
					
				// CAMERA_BLOCK
				case 0x4700:
					fileCameras.push(new Camera3d());
					camerasByName["camera_"+ cams] = fileCameras[fileCameras.length-1];
					currCamera = fileCameras[fileCameras.length-1];
					
					currCamera.setTransform (new All());
					
					var trc:All = currCamera.transform as All;
					
					trc.x = bytes.readFloat() * scaleX;
					trc.z = bytes.readFloat() * scaleY;
					trc.y = bytes.readFloat() * scaleZ;
					
					trc.target = new Object3d();
					
					trc.target.transform.x = bytes.readFloat() * scaleX;
					trc.target.transform.z = bytes.readFloat() * scaleY;
					trc.target.transform.y = bytes.readFloat() * scaleZ;
					
					var bank:Number = bytes.readFloat();
					var lens:Number = bytes.readFloat();
					
					currType = "camera";
					currObj = currCamera;
					cams++;
					break;
					
				// OBJECT_BLOCK
                case 0x4000:
				
					createPolys();
					
					polys = [];
					uvs = [];
					name = readString();
					
					fileObjects.push(new SceneObject());
					objectsByName[name] = fileObjects[fileObjects.length-1];
					
					fileSurfaces.push(new Surface());
					surfacesByName[name] = fileSurfaces[fileSurfaces.length-1];
					
					currSurface = surfacesByName[name];
					currObject = objectsByName[name];
					currType = "mesh";
					currObj = currObject;
					
					break;
					
				// TRANSLATION_MATRIX
				case 0x4160:
				
					var tr:Matrix3d = new Matrix3d();
					tr.a = bytes.readFloat();	tr.c = bytes.readFloat();	tr.b = bytes.readFloat();
					
					var vec:Vector3d = new Vector3d();
					vec.x = tr.a;	vec.y = tr.b; vec.z = tr.c;
					var scX:Number = Math.sqrt(vec.x*vec.x + vec.y*vec.y + vec.z*vec.z);
					tr.a/=scX;	tr.b/=scX;	tr.c/=scX;
					
					tr.i = bytes.readFloat();	tr.k = bytes.readFloat();	tr.j = bytes.readFloat();
					vec.x = tr.i;	vec.y = tr.j; vec.z = tr.k;
					var scZ:Number = Math.sqrt(vec.x*vec.x + vec.y*vec.y + vec.z*vec.z);
					tr.i/=scZ;	tr.j/=scZ;	tr.k/=scZ;
					
					tr.e = bytes.readFloat();	tr.g = bytes.readFloat();	tr.f = bytes.readFloat();
					vec.x = tr.e;	vec.y = tr.f; vec.z = tr.g;
					var scY:Number = Math.sqrt(vec.x*vec.x + vec.y*vec.y + vec.z*vec.z);
					tr.e/=scY;	tr.f/=scY;	tr.g/=scY;
					
					tr.m = bytes.readFloat()*scaleX;	tr.o = bytes.readFloat()*scaleY;	tr.n = bytes.readFloat()*scaleZ;
					
					trans.transpose(tr);
					currObj.transform.transform = tr;
					
					if(currType == "mesh") {
						var pts:Array = currObject.points;
						var pt:Vertex;
						L = currObject.points.length;
						vec = new Vector3d();
						var zv:Vector3d = new Vector3d();
						
						for(i=0; i<L; i++) {
							pt = pts[i];
							vec.x = pt.x;
							vec.y = pt.y;
							vec.z = pt.z;
							
							trans.vectorMul(vec, zv);
							pt.x = zv.x;
							pt.y = zv.y;
							pt.z = zv.z;
						}
					}

					break;
					
				// VERTEX_BLOCK 
                case 0x4110:
					tsize = bytes.readUnsignedShort();
					var x:Number;	var y:Number;	var z:Number;
					for (i=0; i<tsize; i++) {
						x = bytes.readFloat();
						z = bytes.readFloat();
						y = bytes.readFloat();
						currObject.addPoint(x*scaleX, y*scaleY, z*scaleZ);
					}
					break;
					
				// FACE_MATERIAL_BLOCK
				case 0x4130:
					name = readString();	
					tsize = bytes.readShort();
					var mat:Surface = getSurfaceByName(name);
					var polyid:int;
					for(i=0; i<tsize; i++) {
						polyid = bytes.readUnsignedShort();
						polys[polyid].push(mat);
					}
					break;
					
				// FACE_BLOCK
                case 0x4120:
					tsize = bytes.readUnsignedShort();
					var a:int; var b:int; var c:int;	var flag:int;
					for (i=0; i<tsize; i++) {
						a = bytes.readUnsignedShort();
						b = bytes.readUnsignedShort();
						c = bytes.readUnsignedShort();
						flag = bytes.readUnsignedShort();
						if(flipped)
							polys.push([a, b, c]);
						else
							polys.push([c, b, a]);
						
					}
					break;
					
				// TEXTURE_VERTEX_BLOCK
				case 0x4140:
                    tsize = bytes.readUnsignedShort();
					for (i=0; i<tsize; i++)
						uvs.push([bytes.readFloat(), 1-bytes.readFloat()]);
					
					break;
				default:
                    bytes.position += size-6;
			}
		}
		
		private function createPolys () :void {
			if(polys.length > 0 && currObject) {
				var f:Face;
				for(var i:int=0; i<polys.length; i++) {
					if(polys[i][3] is Surface) {
						f = currObject.addPolygon(polys[i][3], polys[i][0], polys[i][1], polys[i][2]);
					}else{
						f = currObject.addPolygon(currSurface, polys[i][0], polys[i][1], polys[i][2]);
					}
					
					if(uvs[polys[i][0]] != null) 
						f.addUvCoord(uvs[polys[i][0]][0], uvs[polys[i][0]][1]);
					if(uvs[polys[i][1]] != null) 
						f.addUvCoord(uvs[polys[i][1]][0], uvs[polys[i][1]][1]);
					if(uvs[polys[i][2]] != null) 
						f.addUvCoord(uvs[polys[i][2]][0], uvs[polys[i][2]][1]);
				
				}
			}
		}
		
		private function readString () :String {
			var c:int;
			var str:String = "";
			while(c = bytes.readByte()) {
				str += String.fromCharCode(c);
			}
			return str;
		}
		
		private function finishParse() :void {
			createPolys();
			if(!blocking) {
				clearInterval(piid);
				piid = -1;
			}
			
			dispatchEvent(new Event(Event.COMPLETE));
			
			bytes = null;
			polys = null;
			uvs = null;
			textures = null;
			currObj = null;
			trans = null;
			clearMemory();
		}
		
	}
}